package ch.epfl.lara.pm.util;

import ch.epfl.lara.pm.ast._
import ch.epfl.lara.pm.transformation.MatchAnalyzer

object Substitution {
  type Substitution = MatchAnalyzer.Substitution
  
  def apply(expr: Expr, subs: List[Substitution]): Expr = expr match {
    case Match(scrutinee,tpe,cases) => Match(scrutinee,tpe,cases.map(this(_,subs)))
    
    case Var(name) => Var(subsitute(name,subs))
      
    case Call(obj,method,args) => Call(this(obj,subs),method,args.map(this(_,subs)))
      
    case Select(obj,field) => Select(this(obj,subs),field)
      
    case If(cond,then,elze) => If(this(cond,subs),this(then,subs),this(elze,subs))
      
    case New(name,args) => New(name,args.map(this(_,subs)))
    
    case IsInstanceOf(e,tpe) => IsInstanceOf(this(e,subs),tpe)

    case Number(n) => expr 
    
    case Plus(left,right) => Plus(this(left,subs),this(right,subs)) 
      
    case Minus(left,right) => Minus(this(left,subs),this(right,subs))
      
    case Prod(left,right) => Prod(this(left,subs),this(right,subs)) 
      
    case Div(left,right) => Div(this(left,subs),this(right,subs))
      
    case Mod(left,right) => Mod(this(left,subs),this(right,subs))
      
    case Eq(left,right) => Eq(this(left,subs),this(right,subs))
      
    case Less(left,right) => Less(this(left,subs),this(right,subs))
      
    case LessEq(left,right) => LessEq(this(left,subs),this(right,subs))
      
    case EAnd(left,right) => EAnd(this(left,subs),this(right,subs))
      
    case EOr(left,right) => EOr(this(left,subs),this(right,subs))
    
    case EImply(left,right) => EImply(this(left,subs),this(right,subs))

    case Not(e) => Not(this(e,subs))

    case NotEq(left,right) => NotEq(this(left,subs),this(right,subs))
    
    case True | False | INone => expr 
      
  }
  
  def apply(kase: CaseDef, subs: List[Substitution]): CaseDef = {
    val CaseDef(pos,pattern,guard,body) = kase
    val evguard = guard match {
      case Some(guard) => Some(this(guard,subs))
      case None => None
    }
    CaseDef(pos,this(pattern,subs),evguard,this(body,subs))
  }
  
  def apply(pattern: PatternDef, subs: List[Substitution]): PatternDef = pattern match {
    case VarPattern(name,tpe) => VarPattern(subsitute(name,subs),tpe)
    case ClassPattern(name,fields) => ClassPattern(name,fields.map(this(_,subs)))
  }
  
  
  private def subsitute(name: PatternName, subs: List[Substitution]): PatternName = subs.find(sub => sub._1 == name.name) match {
    case None => name
    case Some((old,fresh)) => 
      val variable = PatternName(fresh)
      variable.path = name.path
      variable
  }
  
  private def subsitute(name: String, subs: List[Substitution]): String = subs.find(sub => sub._1 == name) match {
    case None => name
    case Some((old,fresh)) => fresh
  }
}
